﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Web.DomainServices;
using System.Web.DomainServices.Providers;
using System.Web.Ria;
using System.Web.Security;
using SoftSource.Timecard.Web.DAL;
using SoftSource.Timecard.Web.Data;
using SoftSource.Timecard.Web.Resources;

namespace SoftSource.Timecard.Web.Services
{
    [EnableClientAccess]
    [RequiresAuthentication]
    public sealed class EntryService : LinqToSqlDomainService<EntryDBDataContext>
    {
        [RequiresRole("Administrator")]
        public IQueryable<EntryLog> GetEntriesPastDueDate()
        {
            var entryIDs = this.DataContext
                .GetEntriesPastDueDate()
                .Select(result => result.EntryLogID);

            List<EntryLog> entries = new List<EntryLog>();
            
            foreach (int id in entryIDs)
            {
                entries.Add(this.DataContext.EntryLogs.SingleOrDefault(entry => entry.EntryLogID == id));
            }

            return entries.AsQueryable();
        }

        [RequiresRole("Administrator")]
        public IQueryable<EntryLog> MarkEntriesAsBilled(int projectID, DateTime? startDate, DateTime endDate)
        {
            this.DataContext.MarkEntriesAsBilled(projectID, startDate, endDate);
            return this.GetUnapprovedEntriesForProject(projectID, startDate, endDate);
        }

        [RequiresRole("Administrator")]
        public IQueryable<EntryLog> GetUnapprovedEntriesForProject(int projectID, DateTime? startDate, DateTime endDate)
        {
            var unapproved = from r in this.DataContext.GetUnapprovedEntriesForProject(projectID, startDate, endDate)
                             join e in this.DataContext.EntryLogs on r.EntryLogID equals e.EntryLogID
                             select e;
            return unapproved.AsQueryable();
        }

        [RequiresRole("Administrator")]
        public IQueryable<LastEntry> GetLatestEntry()
        {
            return this.DataContext
                .LastEntryDetails()
                .Select(result =>
                {
                    return new LastEntry
                    {
                        UserID = result.UserID,
                        Name = result.FullName,
                        EntryDate = result.LastEntry ?? DateTime.MinValue
                    };
                })
                .AsQueryable();
        }

        [RequiresRole("Administrator")]
        public IQueryable<EntryLog> GetFlaggedEntries()
        {
            var entries = from e in this.DataContext.EntryLogs
                          where e.ReviewByAdmin
                          select e;
            return entries;
        }

        [RequiresRole("Administrator", "Project Manager")]
        public IQueryable<EntryLog> GetEntriesPriorToLastBillingDate()
        {
            if (Roles.IsUserInRole(AuthenticationDataResources.ProjectManagerRole))
            {
                AuthenticationService authentication = new AuthenticationService();
                var user = authentication.GetUser();
                var projects = Projects.GetManagingProjects(user.UserID);

                var unapproved = from r in this.DataContext.GetEntriesPriorToLastBillingDate()
                                 join e in this.DataContext.EntryLogs on r.EntryLogID equals e.EntryLogID
                                 join p in projects on e.ProjectID equals p
                                 select e;

                return unapproved
                    .Distinct()
                    .AsQueryable();
            }
            else
            {
                var unapproved = from r in this.DataContext.GetEntriesPriorToLastBillingDate()
                                 join e in this.DataContext.EntryLogs on r.EntryLogID equals e.EntryLogID
                                 select e;
                return unapproved.AsQueryable();
            }
        }

        [RequiresRole("Administrator", "Project Manager")]
        public IQueryable<EntrySummary> GetUtilizationSummaries(DateTime startDate, DateTime endDate)
        {
            //Get entries in a certain date range
            var tempEntries = from e in this.DataContext.EntryLogs
                              where e.EntryDate >= startDate && e.EntryDate <= endDate
                              select new
                              {
                                  e.EntryLogID,
                                  e.EntryDate,
                                  e.Duration,
                                  e.UserID
                              };

            // Transform into DTO types
            var entries = tempEntries
                .AsEnumerable()
                .Select(e => new EntrySummary
                {
                    UserID = e.UserID,
                    Date = new DateTime(e.EntryDate.Year, e.EntryDate.Month, e.EntryDate.Day), // Use only the year, month, and date for nice grouping
                    Hours = e.Duration
                });

            // Aggregate all entries into a single EntrySummary per day per user
            var aggregated = entries
                .GroupBy(e => e.UserID) // Group by user
                .Select(userIDGroup => // For each user...
                    userIDGroup
                    .GroupBy(e => e.Date) // Group by EntryDate
                    .Select(dateGroup => // For each day...
                        dateGroup
                        .Aggregate((left, right) => // Aggregate all entries for the user on this day
                            new EntrySummary
                            {
                                Date = dateGroup.Key,
                                UserID = userIDGroup.Key,
                                Hours = (left.Hours + right.Hours)
                            })))
                .Aggregate(Enumerable.Union); // Collapse into single sequence

            if (aggregated.Count() > 0)
            {                
                var allUsers = aggregated.Select(summary => summary.UserID).Distinct();
                List<EntrySummary> entrySummaries = new List<EntrySummary>();

                // For every user with missing entries on a day, place an empty summary for that day
                for (DateTime date = new DateTime(startDate.Year, startDate.Month, startDate.Day); date <= endDate; date = date.AddDays(1))
                {
                    if (date.DayOfWeek == DayOfWeek.Saturday || date.DayOfWeek == DayOfWeek.Sunday)
                        continue;

                    var users = aggregated.Where(s => s.Date == date).Select(s => s.UserID); // Get all the users that have entries on this day
                    var missing = allUsers.Except(users); // Get the users that have no entry on this day

                    foreach (var user in missing) // For every user that is missing an entry, place an empty summary
                    {
                        entrySummaries.Add(new EntrySummary
                        {
                            UserID = user,
                            Date = date,
                            Hours = 0.0M
                        });
                    }
                }

                entrySummaries.AddRange(aggregated);

                // If the current user is a Project Manager, return only summaries for users that they are allowed to see
                if (Roles.IsUserInRole(AuthenticationDataResources.ProjectManagerRole))
                {
                    AuthenticationService authentication = new AuthenticationService();
                    var user = authentication.GetUser();
                    var users = Users.GetManagingUsers(user.UserID);

                    return entrySummaries
                        .Where(summary => users.Contains(summary.UserID))
                        .AsQueryable();
                }
                else
                {
                    return entrySummaries
                        .AsQueryable();
                }
            }

            return new List<EntrySummary>().AsQueryable();
        }

        #region Filtered Entries
        [RequiresRole("Administrator", "Project Manager")]
        public IQueryable<EntryLog> GetFilteredEntryLogs(
            int? projectID, 
            int? userID, 
            DateTime? startDate, 
            DateTime? endDate)
        {
            IQueryable<EntryLog> baseQueryable = this.DataContext.EntryLogs;
            List<Expression<Func<EntryLog, bool>>> whereExpressions = new List<Expression<Func<EntryLog, bool>>>();

            if (projectID != null)
                whereExpressions.Add(this.GetProjectExpression(projectID.Value));
            if (userID != null)
                whereExpressions.Add(this.GetUserExpression(userID.Value));
            if (startDate != null)
                whereExpressions.Add(this.GetStartDateExpression(startDate.Value));
            if (endDate != null)
                whereExpressions.Add(this.GetEndDateExpression(endDate.Value));

            IQueryable<EntryLog> queryable = baseQueryable;
            if (whereExpressions.Count > 0)
            {
                foreach (var expression in whereExpressions)
                {
                    var temp = queryable;
                    queryable = temp.Where(expression);
                }

                if (Roles.IsUserInRole(AuthenticationDataResources.ProjectManagerRole))
                {
                    AuthenticationService authentication = new AuthenticationService();
                    var user = authentication.GetUser();

                    var projectIDs = Projects.GetManagingProjects(user.UserID);

                    var entries = from p in projectIDs
                                  join e in queryable on p equals e.ProjectID
                                  where e.BillingStateID != BillingState.Billed
                                  select e;
                    return entries
                        .Distinct()
                        .AsQueryable();
                }
                else
                {
                    return queryable;
                }
            }
            else
            {
                return new List<EntryLog>().AsQueryable();
            }
        }

        private Expression<Func<EntryLog, bool>> GetProjectExpression(int projectID)
        {
            return entry => entry.ProjectID == projectID;
        }

        private Expression<Func<EntryLog, bool>> GetUserExpression(int userID)
        {
            return entry => entry.UserID == userID;
        }

        private Expression<Func<EntryLog, bool>> GetStartDateExpression(DateTime startDate)
        {
            return entry => entry.EntryDate >= startDate;
        }

        private Expression<Func<EntryLog, bool>> GetEndDateExpression(DateTime endDate)
        {
            return entry => entry.EntryDate <= endDate;
        }
        #endregion Filtered Entries

        public IQueryable<EntryLog> GetEntriesForCurrentUserByDateRange(DateTime start, DateTime end)
        {
            AuthenticationService authentication = new AuthenticationService();
            var user = authentication.GetUser();

            if (user.IsAuthenticated)
            {
                var entries = from e in this.DataContext.EntryLogs
                              where e.UserID == user.UserID && e.EntryDate >= start && e.EntryDate <= end
                              select e;
                return entries
                    .Distinct()
                    .AsQueryable();
            }
            else
            {
                return new List<EntryLog>().AsQueryable();
            }
        }

        public void InsertEntryLog(EntryLog entryLog)
        {
            var id = this.DataContext.AddTimeEntry(
                entryLog.UserID,
                entryLog.ProjectID,
                entryLog.CategoryID,
                entryLog.EntryDate,
                entryLog.Description,
                entryLog.Duration,
                entryLog.OvernightStay);

            entryLog.EntryLogID = (int)id.First().EntryLogID.Value;

            var entry = this.DataContext.EntryLogs.First(e => e.EntryLogID == entryLog.EntryLogID);

            entryLog.BillableHours = entry.BillableHours;
            entryLog.BillingStateID = entry.BillingStateID;
            entryLog.EntryCreateDate = entry.EntryCreateDate;
            entryLog.EntryDate = entry.EntryDate;           
        }

        public void UpdateEntryLog(EntryLog currentEntryLog)
        {
            if (currentEntryLog.BillingStateID == BillingState.NotApproved)
            {
                currentEntryLog.BillableHours = currentEntryLog.Duration;
            }

            AuthenticationService auth = new AuthenticationService();
            var user = auth.GetUser();

            if (!user.IsInRole(AuthenticationDataResources.AdministratorRole) && !user.IsInRole(AuthenticationDataResources.ProjectManagerRole) && currentEntryLog.BillingStateID != BillingState.NotApproved)
            {
                throw new Exception("The current user is not authorized to change entries once they have been Approved or Billed");
            }
            else
            {
                this.DataContext.EntryLogs.Attach(currentEntryLog, this.ChangeSet.GetOriginal(currentEntryLog));
            }
        }

        public void DeleteEntryLog(EntryLog entryLog)
        {
            this.DataContext.EntryLogs.Attach(entryLog);
            this.DataContext.EntryLogs.DeleteOnSubmit(entryLog);
        }
    }
}


